<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟列表原理 | fixed-size</title>
</head>
<body>
    <style>
        .list {
            width: 200px;
            outline: 1px solid hsl(120deg, 50%, 50%);
        }
        .list-inner{
            position: relative;
        }
        .list-item{
            padding: 10px;
            width: 100%;
            outline: 1px solid hsl(120deg, 50%, 50%);
            box-sizing: border-box;
        }
    </style>
    <div class="list">
        <div class="list-inner"></div>
    </div>
    <script>
        // 参考链接：https://medium.com/ingeniouslysimple/building-a-virtualized-list-from-scratch-9225e8bec120
        const count = 10000;
        const itemHeight = 40;
        const clientHeight = 420;
        const listEl = document.querySelector('.list');
        const listInner = listEl.querySelector('.list-inner');
        /**
         * 虚拟列表核心算法
         * 注意：
         * 1，使用前要设置列表为绝对定位
         * 2，此算法的item是一个固定高度
         * */
        function initVirtualList(props) {
            // bufferSize 缓存数量
            const { listEl, listInner, itemHeight, clientHeight, items, bufferSize = 2 } = props;
            listEl.style.cssText += `height:${clientHeight}px;overflow-x: hidden`;
            return function virtualList(renderItem) {
                const scrollTop = listEl.scrollTop;
                const startIndex = Math.floor(scrollTop / itemHeight);
                const endIndex = Math.min(
                    items.length-1,
                    Math.floor((scrollTop + clientHeight) / itemHeight)
                );
                const startBufferIndex = Math.max(0, startIndex - bufferSize);
                const endBufferIndex = Math.min(items.length-1, endIndex + bufferSize);
                // 顶部偏移量
                const startOffset = startBufferIndex * itemHeight;
                // 尾部偏移量
                const endOffset = (items.length - (endBufferIndex + 1)) * itemHeight;
                listInner.style.setProperty('padding', `${startOffset}px 0 ${endOffset}px 0`);
                const listItems = [];
                for(let i = startBufferIndex; i <= endBufferIndex; i++) {
                    const style = `height: ${itemHeight}px;`;
                    listItems.push(renderItem(items[i], i, style, props))
                }
                return listItems;
            }
        }
        // 模拟1万条数据
        const items = Array.from({ length: count }).map((item, i) => ({ name: `item ${(i+1)}`, checked: false }));
        // 初始化虚拟列表
        const virtualList = initVirtualList({
            listEl,
            listInner,
            itemHeight,
            clientHeight,
            items
        });
        document.addEventListener('DOMContentLoaded', () => {
            const renderItems = virtualList((item, i, style) => {
                return `<div class="list-item" style="${style}">${'item '+(i+1)}</div>`
            });
            listInner.innerHTML = renderItems.join('');
        });
        listEl.addEventListener('scroll', () => {
            const renderItems = virtualList((item, i, style) => {
                return `<div class="list-item" style="${style}">${item.name}</div>`
            });
            listInner.innerHTML = renderItems.join('');
        });
    </script>
</body>
</html>